/**
 *
 */
package com.honeybees.framework.spring.mvc;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.List;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.honeybees.framework.pagination.Page;
import com.honeybees.framework.spring.mvc.model.ModelSupport;
import com.honeybees.framework.spring.mvc.model.ReturnMessage;
import com.honeybees.framework.sso.SessionConsts;
import com.honeybees.framework.sso.session.MockSession;
import com.honeybees.framework.sso.session.manager.MockSessionManager;
import com.honeybees.framework.sso.vo.LoginUser;

/**
 * <dl>
 * <dt><b> 控制器层入口 </b></dt>
 * <p>
 * <dd>功能描述</dd>
 * </dl>
 * <p>
 * Copyright (C) All rights reserved.
 * </p>
 *
 * @param <T> 模型
 * @author 李远明
 * @version 2016-07-08 21:01 DONE_MESSAGE、FAIL_MESSAGE
 * @see org.springframework.stereotype.Controller
 * @since 2015-10-23 14:51:48
 */
@Component
public class WebController<T extends ModelSupport> {

    /**
     * 日志
     */
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 成功返回码
     */
    protected static final String CODE_DONE = ReturnMessage.CODE_DONE;
    /**
     * 失败返回码
     */
    protected static final String CODE_FAIL = ReturnMessage.CODE_FAIL;

    protected HttpServletRequest request;
    protected HttpServletResponse response;

    /**
     * 操作返回信息
     *
     * @version 2015-11-01 15:50:00 responseInfo -> returnMsg
     */
    protected ReturnMessage returnMsg = null;
    /**
     * 成功信息
     *
     * @version 2015-11-01 16:00:21 新建
     * @version 2016-07-08 21:00 DONE_MESSAGE
     */
    protected static final ReturnMessage DONE_MESSAGE = ReturnMessage.DONE_MESSAGE;
    /**
     * 失败信息
     *
     * @version 2015-11-01 16:00:21 新建
     * @version 2016-07-08 21:00 FAIL_MESSAGE
     */
    protected static final ReturnMessage FAIL_MESSAGE = ReturnMessage.FAIL_MESSAGE;

    /**
     * Web运行时目录（WEBROOT物理路径）
     *
     * @version 2014-11-23 11:40:41
     */
    protected static String webRoot;

    /**
     * 页面数据结构
     *
     * @version 2016-08-22 10:28:35 page = new Page<T>()
     * @see #initPage
     */
    protected final Page<T> page = new Page<T>();

    /**
     * Session 管理
     *
     * @version 2016-01-08 17:13:10
     */
    @Resource
    private MockSessionManager mockSessionManager;

    public void setRequest(HttpServletRequest request) {
        // webRoot = request.getServletContext().getRealPath("/");
        this.returnMsg = new ReturnMessage(CODE_DONE, "操作成功"); // 每次请求前，重置返回信息。
        ReturnMessage.DONE_MESSAGE.setMessage(""); // reset 2017-06-15 17:14:25
        ReturnMessage.FAIL_MESSAGE.setMessage(""); // reset 2017-06-15 17:14:25
        this.page.setTotal(0);
        this.page.setRows(Collections.emptyList());
        this.request = request;
    }

    public void setResponse(HttpServletResponse response) {
        this.response = response;
    }

    /**
     * <dl>
     * <dt><b> 设置分页变量 </b></dt>
     * <dd>
     * <p>
     * <pre>
     * setPageParameter(request, object);
     * initPage(service.select(object), object);
     * </pre>
     * <p>
     * </dd>
     * </dl>
     *
     * @param request {@linkplain HttpServletRequest Web请求}
     * @param model   {@linkplain ModelSupport 实体类，用于设置分页参数}
     * @author 李远明
     * @version 2015-10-26 22:50:11 新建
     * @version 2015-10-27 14:53:33 分页时，请求参数一般有两种：pageNo、pageSize（EasyUI 为 page、rows） 或
     * offset、limit；如果是 offset、limit，可以直接用于SQL查询；如果是 pageNo、pageSize，则需要计算
     * offset、limit，公式如下：
     * <p>
     * <pre>
     *          pageNo = (pageNo == 0) ? 1 : pageNo;
     *          int offset = (pageNo - 1) * pageSize;
     *          int limit = pageSize;
     *          </pre>
     * @see com.honeybees.framework.mybatis.MyBatisDaoSupport#selectPage
     */
    protected void setPageParameter(HttpServletRequest request, ModelSupport model) {

        // 现在使用 bootstrap-table，分页请求参数为 offset、limit，不用计算；如果使用 pageNo、pageSize 的分页方式，请用如下公式计算：

        // int pageNo = Utils.safeToInt(request.getParameter("pageNo"));
        // int pageSize = Utils.safeToInt(request.getParameter("pageSize"));
        //
        // int offset = PageUtils.calcOffset(pageNo, pageSize);
        // // int limit = pageSize;
        //
        // model.setOffset(offset);
        // model.setLimit(pageSize);
    }

    /**
     * <dl>
     * <dt><b> 初始化页面数据 </b></dt>
     * <dd>
     * <p>
     * <pre>
     * setPageParameter(request, object);
     * initPage(service.select(object), object);
     * </pre>
     * <p>
     * </dd>
     * </dl>
     *
     * @param rowsList 数据
     * @param model    {@linkplain ModelSupport#getTotalCount() 查询对象}，用于取得 <b>数据总条数</b>
     * @author 李远明
     * @version 2015-10-26 22:53:43
     */
    protected void initPage(List<T> rowsList, ModelSupport model) {
        synchronized (this.page) {
            // page = page.newInstance();
            int count = model.getTotalCount(); // 数据总条数，可能为零
            if (rowsList == null || rowsList.isEmpty()) {
                count = 0;
                rowsList = Collections.emptyList();
            } else {
                if (count == 0) {
                    count = rowsList.size();
                }
            }
            this.page.setTotal(count);
            this.page.setRows(rowsList);
        }
    }

    /**
     * <DL>
     * <DT><B> 标题 </B></DT>
     * <DD>功能描述</DD>
     * </DL>
     *
     * @return
     * @version 2014-11-23 11:30:39
     */
    protected static String getWebRoot() {
        return webRoot;
    }

    /**
     * <dl>
     * <dt><b> 从当前 HTTP 请求（request）中，得到 SessionId </b></dt>
     * <p>
     * <dd>
     * <p style='color:red'>
     * 这个值在 SSOInterceptor 中设置，不能为空
     * </p>
     * </dd>
     * </dl>
     *
     * @param request 当前请求
     * @return SessionId
     * @author 李远明
     * @see com.honeybees.framework.sso.interceptor.SSOInterceptor#preHandle
     */
    // request.setAttribute()和getAttribute()方法传递的数据只会存在于Web容器内部，在具有转发关系的Web组件之间共享。
    protected String getSessionId(HttpServletRequest request) {
        Object sessionId = request.getAttribute(SessionConsts.KEY_SESSION_ID_IN_REQUEST);
        if (sessionId == null) {
            String msg = "getSessionId 错误，请在 Spring 配置文件中配置 SSOInterceptor 拦截器。";
            IllegalStateException ex = new IllegalStateException(msg);
            this.logger.error("getSessionId error", ex);
            throw ex;
        }
        return sessionId.toString();
    }

    /**
     * <dl>
     * <dt><b> 从 HTTP 请求中，得到 {@linkplain MockSession Session} </b></dt>
     * <p>
     * <dd>
     * <p style='color:red'>
     * 从 HTTP Attribute 中获取，返回null，表示用户未登录
     * </p>
     * </dd>
     * </dl>
     *
     * @param request 当前请求
     * @return Session对象
     * @author 李远明
     * @see com.honeybees.framework.sso.interceptor.SSOInterceptor#preHandle
     */
    protected MockSession getSession(HttpServletRequest request) {
        Object object = request.getAttribute(SessionConsts.KEY_SESSION_IN_REQUEST);
        if (object == null) { // 用户未登录
            return null;
        }
        MockSession session = (MockSession) object;
        this.mockSessionManager.getSessionIdByUserId(session.getUserId());
        return session;
    }

    /**
     * <dl>
     * <dt><b> 从 HTTP 请求中，得到当前登录的用户 </b></dt>
     * <p>
     * <dd>
     * <p style='color:red'>
     * 从 HTTP Attribute 中获取，返回null，表示用户未登录
     * </p>
     * </dd>
     * </dl>
     *
     * @param request 当前请求
     * @return 当前登录用户
     * @author 李远明
     * @version 2016-01-08 17:35:55
     */
    protected LoginUser getLoginUser(HttpServletRequest request) {
        MockSession session = this.getSession(request);
        return session == null ? null : session.getUser();
    }

    /**
     * <dl>
     * <dt><b> 从 HTTP 请求中，得到当前登录的用户的ID </b></dt>
     * <p>
     * <dd>
     * <p style='color:red'>
     * 从 HTTP Attribute 中获取，返回null，表示用户未登录
     * </p>
     * </dd>
     * </dl>
     *
     * @param request 当前请求
     * @return 当前登录用户的ID
     * @author 李远明
     * @version 2016-01-08 17:36:49
     */
    protected String getLoginUserId(HttpServletRequest request) {
        LoginUser user = this.getLoginUser(request);
        return user == null ? null : user.getId();
    }

    /**
     * <dl>
     * <dt><b> 从 HTTP 请求中，得到当前登录的用户的名称 </b></dt>
     * <p>
     * <dd>
     * <p style='color:red'>
     * 从 HTTP Attribute 中获取，返回null，表示用户未登录
     * </p>
     * </dd>
     * </dl>
     *
     * @param request 当前请求
     * @return 当前登录用户的名称
     * @author 李远明
     * @version 2016-01-08 17:43:46
     */
    protected String getLoginUsername(HttpServletRequest request) {
        LoginUser user = this.getLoginUser(request);
        return user == null ? null : user.getUsername();
    }

    /**
     * <dl>
     * <dt><b> 从 缓存中，得到用户 Session </b></dt>
     * <p>
     * <dd>
     * <p style='color:red'>
     * 返回null，表示用户未登录
     * </p>
     * </dd>
     * </dl>
     *
     * @param userId 用户编号
     * @return 用户的 Session 信息
     * @author 李远明
     * @version 2016-01-08 17:35:41
     */
    protected MockSession getSessionByUserId(String userId) {
        String sessionId = this.mockSessionManager.getSessionIdByUserId(userId);
        if (sessionId != null && sessionId.length() != 0) {
            return this.mockSessionManager.getMockSession(sessionId);
        } else {
            return null;
        }
    }

    /**
     * <dl>
     * <dt><b> 将信息响应给前台 </b></dt>
     * <p>
     * <dd></dd>
     * </dl>
     *
     * @param response 当前响应
     * @param obj      信息内容
     * @author 李远明
     * @version 2016-07-26 14:53:41
     */
    // response.getOutputStream().write(data.getBytes("UTF-8"));
    // response.getWriter().write(data);
    protected void write(HttpServletResponse response, Object obj) {
        response.setContentType("text/html; charset=utf-8");
        try {
            PrintWriter out = response.getWriter();
            response.reset();
            out.write(JSON.toJSONString(obj));
        } catch (IOException e) {
            this.logger.error("write error", e);
        }
    }

}
